/* eslint-disable no-console */

import { noise } from '@chainsafe/libp2p-noise'
import { yamux } from '@chainsafe/libp2p-yamux'
import { floodsub } from '@libp2p/floodsub'
// import { gossipsub } from '@chainsafe/libp2p-gossipsub'
import { mplex } from '@libp2p/mplex'
import { tcp } from '@libp2p/tcp'
import { createLibp2p } from 'libp2p'
import { identifyService } from 'libp2p/identify'
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
import pMap from 'p-map';
import { peerIdToDid } from '../peer-id.js'
import Logger from 'logplease'

const logger = Logger.create('test')
Logger.setLogLevel('DEBUG')

const topic = 'news'

const createNode = async (name) => {
  const node = await createLibp2p({
    addresses: {
      listen: ['/ip4/0.0.0.0/tcp/0']
    },
    transports: [tcp()],
    streamMuxers: [yamux(), mplex()],
    connectionEncryption: [noise()],
    services: {
      pubsub: floodsub(),
      // pubsub: gossipsub({ allowPublishToZeroPeers: true, emitSelf: false }),
      identify: identifyService()
    },
    connectionManager: {
      minConnections: 300,
      maxConnections: 1000
    }
  })

  logger.debug(`Node ${name} started with id ${node.peerId.toString()}`)
  // logger.debug('Listening on:')
  // node.getMultiaddrs().forEach((ma) => logger.debug(ma.toString()))

  // node.services.pubsub.subscribe(topic)
  // node.services.pubsub.addEventListener('message', (evt) => {
  //   logger.debug(`${name} received: ${uint8ArrayToString(evt.detail.data)} on topic ${evt.detail.topic}`)
  // })

  return node
}

const waitForChannelReady = async (node, did) => {
  node.services.pubsub.subscribe(`${did}-channel`)
  let waitIntervalId_2
  const waitChannelReady = () => {
    return new Promise((resolve, reject) => {
      logger.debug(`wait for channal ${did} is ready...`)
      waitIntervalId_2 = setInterval(() => {
        node.services.pubsub.subscribe(`${did}-channel`)
        const peers = node.services.pubsub.getSubscribers(`${did}-channel`)
        // logger.debug(`channel ${did} peers`, peers.map(item => item.toString()))
        if (peers.length > 0) {
          logger.debug(`channel ${did} is ready!!!!!`)
          resolve()
        }
      }, 1000)
    })
  }
  await waitChannelReady()
  clearInterval(waitIntervalId_2)
}

const createServer = async (name, hbCount) => {
  const node = await createNode(name)
  node.addEventListener('peer:connect', (evt) => {
    let peerId = evt.detail.toString()
    logger.debug('Connected to %s', peerId) // Log connected peer
    let cDid = peerIdToDid(peerId)
    node.services.pubsub.subscribe(`${cDid}-channel`)

    let connections = node.components.connectionManager.connections
    logger.debug('connections size', connections.size)
    if (connections.size == hbCount) {
      logger.debug('server: start beart beat')
      let body = {
        type: 'start-heart-beat',
        name: 'server'
      }

      const dids = []
      connections.map.forEach((value, key) => {
        let _did = peerIdToDid(key)
        dids.push(_did)
      })
      console.log(dids)

      const mapper = async (did) => {
        await waitForChannelReady(node, did)
        node.services.pubsub.publish(`${did}-channel`, uint8ArrayFromString(JSON.stringify(body)))
      }
      pMap(dids, mapper)
    }
  })

  node.services.pubsub.addEventListener('message', async (evt) => {
    const topic = evt.detail.topic
    const from = evt.detail.from.toString()
    const msg = uint8ArrayToString(evt.detail.data)

    if (topic.endsWith('-channel')) {
      const channelDid = topic.split('-')[0]
      const body = JSON.parse(msg)
      logger.debug(`server received: ${msg} from ${body.name}}`)

      if (body.type == 'heart-beat') {
        node.services.pubsub.publish(`${channelDid}-channel`, uint8ArrayFromString(JSON.stringify({
          type: 'heart-beat-res',
          name: 'server'
        })))
      }
    }
  })
  return node
}


const createClient = async (name, server, heartbeatInterval) => {
  const node = await createNode(name)
  let peerId = node.peerId.toString()
  let did = peerIdToDid(peerId)

  node.services.pubsub.subscribe(`${did}-channel`)

  // Add node's 2 data to the PeerStore
  await node.peerStore.patch(server.peerId, {
    multiaddrs: server.getMultiaddrs()
  })
  await node.dial(server.peerId)

  node.services.pubsub.addEventListener('message', async (evt) => {
    const topic = evt.detail.topic
    const from = evt.detail.from.toString()
    const msg = uint8ArrayToString(evt.detail.data)

    if (topic.endsWith('-channel')) {
      const channelDid = topic.split('-')[0]
      const body = JSON.parse(msg)
      logger.debug(`${name} received: ${msg} from ${body.name}}`)

      if (body.type == 'start-heart-beat') {
        logger.debug(`${name} start heart beat`)
        setInterval(() => {
          node.services.pubsub.publish(`${did}-channel`, new TextEncoder().encode(JSON.stringify({ type: 'heart-beat', name, timestamp: Date.now() })));
        }, heartbeatInterval)
      }
    }
  })
  
}

const sleep = (maxTimeout) => {
  const rand = Math.random()
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve()
    }, maxTimeout*rand)
  })
}

;(async () => {

  const count = 10
  const server = await createServer('server', count)

  const range = []
  for (let i = 0; i < count; i++) range.push(i+1)

  const mapper = async (i) => {
    await sleep(30000)
    createClient('client_' + i, server, 500)
  }
  pMap(range, mapper)

  // for (let i = 0; i < count; i++) {
  //   await createClient('client_' + (i+1), server, 500)
  //   await sleep()
  // }

  

  // node2 publishes "news" every second
  // setInterval(() => {
  //   server.services.pubsub.publish(topic, uint8ArrayFromString('Bird bird bird, bird is the word!')).catch(err => {
  //     console.error(err)
  //   })
  // }, 1000)
})()
